package net.comfycastle.musicplayer;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;

import android.content.ContentUris;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore.Images;
import android.widget.ImageView;

/**
 * 앨범 아트 이미지를 읽어와 ImageView에 붙여주는 클래스
 * 앨범아트마다 별도의 스레드를 사용해 로딩하며,
 * 따라서 한 번에 여러 번 호출해도 문제 없다.
 * @author ricale
 *
 */
public class AlbumArtManager {
	///////////
	//////////
	/////////   enum
	
	/**
	 * 앨범 아트 비트맵의 크기를 의미하는 enum 클래스
	 *
	 */
	public enum SIZE {
		/** 작은 크기. 리스트 항목 뷰에 들어갈 크기다. */
		SMALL,
		/** 중간 크기. 앨범 내부 목록 프래그먼트에서 정보로써 보여주기 위한 크기다. */
		MEDIUM,
		/** 큰 크기. 재생 액티비티에서 배경으로 쓰기 위한 크기다. */
		BIG
	} // end SIZE
	
	///////////
	//////////
	/////////   constant
	
	private static int SLEEPING_TIME_BEFORE_LOADING_ART = 800;
	
	///////////
	//////////
	/////////   variable
	
	/** 현재 컨텍스트. Uri와 Bitmap 생성에 활용된다. */
	private Context mContext;
	/** 로딩이 끝난 앨범 아트를 이미지뷰에 붙이기 위한 핸들러 */
	private AlbumArtHandler mHandler;
	
	private HashMap<Uri, Bitmap> mLoadedArts;
	
	/** 이미지를 로딩하기 직전에 약간의 대기 시간을 가질 것인가 아닌가. 한꺼번에 많은 앨범아트를 불러올 때는 (여러 번 호출할 때는) 대기 시간을 갖는 것이 권장된다. */
	private boolean mIsSetDelayingTime;
	
	/** 앨범 아트 비트맵 (최대) 너비. 기본적으로 SIZE.SMALL에 해당하는 값을 갖고 있다. */
	private int mArtWidth;
	/** 앨범 아트 비트맵 (최대) 높이. 기본적으로 SIZE.SMALL에 해당하는 값을 갖고 있다. */
	private int mArtHeight;
	
	///////////
	//////////
	/////////   constructor
	
	/**
	 * 생성자
	 * @param context 이 클래스를 생성한 컨텍스트
	 */
	public AlbumArtManager(Context context) {
		mContext = context;
		mHandler = new AlbumArtHandler();
		mIsSetDelayingTime = true;
		
		mLoadedArts = new HashMap<Uri, Bitmap>();
		
		mArtWidth = (int)context.getResources().getDimension(R.dimen.albumart_width);
		mArtHeight = (int)context.getResources().getDimension(R.dimen.albumart_height);
	} // end AlbumArtLoader
	
	///////////
	//////////
	/////////   setter
	
	/**
	 * 이미지를 로딩하기 직전에 약간의 대기 시간을 가질 것인가 아닌가. 한꺼번에 많은 앨범아트를 불러올 때는 (여러 번 호출할 때는) 대기 시간을 갖는 것이 권장된다.
	 * @param is 대기시간을 가질 것이라면 true, 아니라면 false, 기본 값은 true로 설정되어 있다.
	 * @return 객체 자기 자신. 연결된 호출을 위해 리턴한다.
	 */
	public AlbumArtManager setDelayed(boolean is) {
		mIsSetDelayingTime = false;
		return this;
	} // end setDelayed
	
	/**
	 * 앨범 아트 비트맵의 크기를 설정한다.
	 * 인자로 enum SIZE의 SMALL, MEDIUM, BIG을 받는데,
	 * 기본적으로 SMALL에 해당하는 값으로 설정되어 있어 SMALL로 사용할 경우에는 호출할 필요가 없다.
	 * @param s 앨범 아트 비트맵의 크기
	 * @return 자기 자신. 연결된 호출을 위해 리턴한다.
	 */
	public AlbumArtManager setSize(SIZE s) {
		switch(s) {
		case SMALL:
			mArtWidth = (int)mContext.getResources().getDimension(R.dimen.albumart_width);
			mArtHeight = (int)mContext.getResources().getDimension(R.dimen.albumart_height);
			break;
		case MEDIUM:
			mArtWidth = (int)mContext.getResources().getDimension(R.dimen.albumjacket_width);
			mArtHeight = (int)mContext.getResources().getDimension(R.dimen.albumjacket_height);
			break;
		case BIG:
			break;
		} // end switch
		return this;
	} // end setSize
	
	///////////
	//////////
	/////////   methods
	
	/**
	 * 앨범 아트 로딩을 요청한다. 앨범 아트 로딩이 끝나면 인자로 넘긴 이미지뷰에 자동으로 붙는다.
	 * @param view 앨범 아트가 붙여질 이미지뷰
	 * @param albumId 앨범 아트를 가진 앨범이 미디어 스토어에서 갖는 아이디
	 */
	public void requestAlbumArt(ImageView view, long albumId) {
		if(albumId != 0) {
			Uri uri = ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId);
			view.setTag(uri);
			new Thread(new AlbumArtLoader(mContext, new AlbumArtData(view, uri))).start();
		} // end if
	} // end requestAlbumArt
	
	/**
	 * 갖고 있는 모든 앨범아트 비트맵의 메모리를 해제한다. AlbumArtManager 사용이 끝나면 호출해야 한다.
	 */
	public void release() {
		Iterator<Bitmap> i = mLoadedArts.values().iterator();
		while(i.hasNext()) {
			i.next().recycle();
		} // end while
		
		mLoadedArts.clear();
	} // end release
	
	///////////
	//////////
	/////////   inner class
	
	/**
	 * 앨범 아트 로딩에 관련된 데이터들의 구조체 클래스
	 *
	 */
	private class AlbumArtData {
		/** 앨범 아트가 붙을 이미지뷰 */
		public ImageView imageView;
		/** 앨범 아트의 위치 (uri) */
		public Uri uri;
		/** 앨범 아트 비트맵 이미지 */
		public Bitmap albumArt;
		
		/**
		 * 생성자
		 * @param a 이미지뷰와 uri가 저장되어 있는 AlbumArtData 객체
		 * @param b 앨범 아트 비트맵 이미지
		 */
		public AlbumArtData(AlbumArtData a, Bitmap b) {
			imageView = a.imageView;
			uri = a.uri;
			albumArt = b;
		} // end AlbumArtData
		
		/**
		 * 생성자
		 * @param i 앨범 아트가 붙을 이미지뷰
		 * @param u 앨범 아트의 위치 (uri)
		 */
		public AlbumArtData(ImageView i, Uri u) {
			imageView = i;
			uri = u;
		} // end AlbumArtData
	} // end AlbumArtData
	
	/**
	 * 앨범 아트를 로딩할 러너블 클래스
	 *
	 */
	private class AlbumArtLoader implements Runnable {
		/** 현재 컨텍스트 */
		private Context context;
		/** 앨범 아트 로딩에 필요한 데이터 구조체 */
		private AlbumArtData albumArtData;
		
		/**
		 * 생성자
		 * @param context 현재 컨텍스트
		 * @param a 로딩할 앨범 아트와 관련된 정보 객체
		 */
		public AlbumArtLoader(Context context, AlbumArtData a) {
			this.context = context;
			albumArtData = a;
		} // end AlbumArtThread
		
		/**
		 * 스레드 본체.
		 * 앨범 아트를 로드해와서 핸들러(AlbumArtHandler)에게 전달한다.
		 * 한번 로딩된 적 있는 비트맵이라면 그것을 재활용한다.
		 */
		public void run() {
			Bitmap loaded = mLoadedArts.get(albumArtData.uri);
			if(loaded != null) {
				Message msg = new Message();
				msg.obj = new AlbumArtData(albumArtData, loaded);;
				mHandler.sendMessage(msg);
			} else {
				try {	
					if(mIsSetDelayingTime) {
						Thread.sleep(SLEEPING_TIME_BEFORE_LOADING_ART);
					} // end if
					
					Bitmap bitmap = Images.Media.getBitmap(context.getContentResolver(), albumArtData.uri);
					
					int bitmapWidth = bitmap.getWidth();
					int bitmapHeight = bitmap.getHeight();
					
					// 이하 if문의 공식은 화면에 보일 앨범 아트의 가로 세로 길이(albumArtWidth, albumArtHeight)가 같기 때문에 적용될 수 있는 식이다.
					if(bitmapWidth > bitmapHeight) {
						float calculated = (float)bitmapHeight * ((float)mArtWidth / (float)bitmapWidth); 
						bitmapHeight = (int)calculated;
						bitmapWidth = mArtWidth;
					} else if (bitmapWidth < bitmapHeight) {
						float calculated = (float)bitmapWidth * ((float)mArtHeight / (float)bitmapHeight); 
						bitmapWidth  = (int)calculated;
						bitmapHeight = mArtHeight;
					} else {
						bitmapWidth = mArtWidth;
						bitmapHeight = mArtHeight;
					} // end if
					
					Bitmap scaled = Bitmap.createScaledBitmap(bitmap, bitmapWidth, bitmapHeight, true);
					mLoadedArts.put(albumArtData.uri, scaled);
						
					if(albumArtData.imageView.getTag().equals(albumArtData.uri)) {
						Message msg = new Message();
						msg.obj = new AlbumArtData(albumArtData, scaled);
						mHandler.sendMessage(msg);
					} // end if
					
				} catch (InterruptedException ie) {
					ie.printStackTrace();
				} catch (IOException ioe) {
					ioe.printStackTrace();
				} // end try-catch
			} // end if 
		} // end run
	} // end AlbumArtThread
	
	/**
	 * 로딩된 앨범 아트를 해당 이미지뷰에 붙이는 작업을 위한 핸들러
	 *
	 */
	private static class AlbumArtHandler extends Handler {
		/**
		 * 핸들러 본체
		 * 앨범 아트를 해당 이미지 뷰에 붙인다.
		 */
		public void handleMessage(Message msg) {
			AlbumArtData o = (AlbumArtData)msg.obj;
			if(o.imageView.isShown() && o.imageView.getTag().equals(o.uri)) {
				o.imageView.setImageBitmap(o.albumArt);
			} // end if
		} // end handleMessage
	} // end AlbumArtHandler
} // end AlbumArtLoader